package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.rep.GoodsRep;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodsRep goodsRep;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;


    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        //根据skuId获取数据
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setId(skuId);
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setTitle(skuInfo.getSkuName());
            goods.setCreateTime(new Date());
            goods.setPrice(productFeignClient.getSkuPrice(skuId).doubleValue());
            return skuInfo;
        });
        CompletableFuture<Void> tmCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(skuInfo.getTmId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        });
        CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
        });
        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
            List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        });
        CompletableFuture.allOf(
                skuInfoCompletableFuture,
                tmCompletableFuture,
                categoryCompletableFuture,
                attrCompletableFuture
        ).join();
        goodsRep.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        goodsRep.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        String key = "hotScore";
        Double score = redisTemplate.opsForZSet().incrementScore(key, skuId, 1);
        if (score % 10 == 0) {
            Optional<Goods> optional = goodsRep.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(score.longValue());
            goodsRep.save(goods);
        }

    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        //生成dsl语句
        SearchRequest searchRequest = this.buildDsl(searchParam);
        //执行dsl语句
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //将查询数据封装到searchResponseVo
        SearchResponseVo searchResponseVo = this.parseResult(searchResponse);
        //赋值每页记录数
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //赋值页数
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //计算总页数
//        Long totalPages = searchResponseVo.getTotal() % searchParam.getPageSize() == 0 ? searchResponseVo.getTotal() % searchParam.getPageSize() : searchResponseVo.getTotal() % searchParam.getPageSize() + 1;
        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageNo() - 1) / searchParam.getPageSize();
        //赋值总页数
        searchResponseVo.setTotalPages(totalPages);
        //返回数据
        return searchResponseVo;
    }

    //生成dsl语句
    private SearchRequest buildDsl(SearchParam searchParam) {
        //一个查询器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //bool
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //根据分类Id查询
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())) {
            //bool->filter->term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())) {
            //bool->filter->term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())) {
            //bool->filter->term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }
        //根据keyword查询，
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            //bool->must->match
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
            //对查询词汇设置高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            highlightBuilder.preTags("<span style=color:red >");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        //用户通过品牌进行过滤
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] split = trademark.split(":");
            if (split != null && split.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
            }
        }
        //根据平台属性过滤
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            //遍历数组
            for (String prop : props) {
                //分割取出内部数据
                String[] split = prop.split(":");
                if (split != null && split.length == 3) {
                    //创建内部bool
                    BoolQueryBuilder innerBoolQueryBuilder = QueryBuilders.boolQuery();
                    //创建过滤条件
                    boolQueryBuilder.filter(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    boolQueryBuilder.filter(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                    //将内部的bool，传递到外层
                    //filter->netest
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", innerBoolQueryBuilder, ScoreMode.None));
                }
            }
        }
        //设置分页
        int form = (searchParam.getPageNo() - 1) * searchParam.getPageSize();
        searchSourceBuilder.from(form);
        searchSourceBuilder.size(searchParam.getPageSize());
        //设置排序
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //分割获取数据
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                //声明一个排序条件
                String orderField = "";
                //判断根据什么排序
                switch (split[0]) {
                    case "1":
                        orderField = "hotScore";
                        break;
                    case "2":
                        orderField = "price";
                        break;
                }
                //封装排序条件
                searchSourceBuilder.sort(orderField, "asc".equals(split[1]) ? SortOrder.ASC : SortOrder.DESC);
            }
        }
        //query->bool
        searchSourceBuilder.query(boolQueryBuilder);
        //聚合查询
        //品牌聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")
                        .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                        .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"))
        );
        //平台属性聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg", "attrs")
                        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue")))
        );
        //查询
        SearchRequest searchRequest = new SearchRequest("goods");

        searchSourceBuilder.fetchSource(new String[]{"id", "defaultImg", "title", "price"}, null);
        //将生成的DSL语句赋值给对象
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

    private SearchResponseVo parseResult(SearchResponse searchResponse) {
        //创建SearchResponseVo对象，便于赋值
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //获取总条数,
        SearchHits hits = searchResponse.getHits();
        //赋值总记录数
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //获取sku集合
        List<Goods> goodsList = new ArrayList<>();
        //获取商品集合
        SearchHit[] sukHits = hits.getHits();
        //遍历获取数据
        for (SearchHit sukHit : sukHits) {
            //获取到单个商品的JSON信息
            String goodsJSON = sukHit.getSourceAsString();
            //将JSON数据转换为Goods类型
            Goods goods = JSON.parseObject(goodsJSON, Goods.class);
            //判断是否根据关键词查询
            if (sukHit.getHighlightFields().get("title") != null) {
                //将goods中的title替换成高亮的title
                goods.setTitle(sukHit.getHighlightFields().get("title").getFragments()[0].toString());
            }
            //将goods数据添加到集合中
            goodsList.add(goods);
        }
        //将sku集合赋值到SearchResponseVo对象中
        searchResponseVo.setGoodsList(goodsList);


        //获取聚合map
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //获取品牌集合
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //遍历获取数据
        List<SearchResponseTmVo> searchResponseTmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
            //创建品牌对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //获取品牌Id
            String tmId = bucket.getKeyAsString();
            //封装品牌Id
            searchResponseTmVo.setTmId(Long.parseLong(tmId));
            //获取品牌名称
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            //封装品牌名称
            searchResponseTmVo.setTmName(tmName);
            //获取品牌图片URL
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            String tmUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            //封装图片URL
            searchResponseTmVo.setTmLogoUrl(tmUrl);
            //返回对象封装成集合
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        //将searchResponseTmVoList集合赋值到SearchResponseVo对象中
        searchResponseVo.setTrademarkList(searchResponseTmVoList);


        //  获取平台属性数据：attrs 数据类型是nested.
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");
        //获取平台属性集合
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        //便利获取数据
        List<SearchResponseAttrVo> searchResponseAttrVoList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //获取平台属性Id并赋值
            String attrId = bucket.getKeyAsString();
            searchResponseAttrVo.setAttrId(Long.parseLong(attrId));
            //获取平台属性值名并赋值
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);
            //获取平台属性值聚合
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            //获取平台属性值名集合

            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(bucket1 ->
                bucket1.getKeyAsString()).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueNameList);
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //将searchResponseAttrVoList赋值到searchResponseVo中
        searchResponseVo.setAttrsList(searchResponseAttrVoList);

        return searchResponseVo;
    }

}
